﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Common.Utilities.Dispatcher;
using Workflows.Components.Entities.Distances;

namespace Workflows.Components.Entities.Relations.InferredAssociations
{
    public class RuleBasedCollapseCalculator:ICollapseCalculator
    {
        #region ICollapseCalculator Members

        public event WorkerStatusChanged OnStatusChanged;

        public void TryCollapse(
            string idAttrName, string collapsedIDAttrName,
            List<AttributeMetaData> attrMetaDatas,
            Dictionary<string, IAttributeDistanceCalcutor> attrComparers,
            Dictionary<string, Entity> entities, double nativeAttrThreshold,
            double associativeAttrThreshold, double collapseScoreThreshold,
            ref Dictionary<int, Dictionary<string, double>> collapsedEntities,
            ref Dictionary<int, Dictionary<string, object>> collapsedInstances)
        {
            if (collapsedEntities == null)
                collapsedEntities = new Dictionary<int, Dictionary<string, double>>();
            collapsedEntities.Clear();
            if (collapsedInstances == null)
                collapsedInstances = new Dictionary<int, Dictionary<string, object>>();
            collapsedInstances.Clear();

            Dictionary<string, Dictionary<string, double>> entity_entity_similarities =
                new Dictionary<string, Dictionary<string, double>>();

            string[] entityIDs = new string[entities.Count];
            entities.Keys.CopyTo(entityIDs, 0);
            for (int i = 0; i < entityIDs.Length; i++)
            {
                string entityID1 = entityIDs[i];
                Entity entity1 = entities[entityID1];
                Dictionary<string, double> toEntitySimilarities = new Dictionary<string, double>();
                for (int k = i + 1; k < entityIDs.Length; k++)
                {
                    string entityID2 = entityIDs[k];
                    Entity entity2 = entities[entityID2];
                    double similarity = 0;

                    if (this.CanCollapse(
                        attrMetaDatas, attrComparers, nativeAttrThreshold,
                        associativeAttrThreshold, collapseScoreThreshold,
                        entity1, entity2, ref similarity))
                    {
                        toEntitySimilarities.Add(entity2.Id, similarity);
                    }
                }
                entity_entity_similarities.Add(entity1.Id, toEntitySimilarities);

                if(this.OnStatusChanged !=null && (i+1) % 1000==0)
                {
                    string msg = "Rule-based collapse: computing entity pair-wise distances, " +
                                 "processed " + i + " or " + entities.Count + " entities";
                    int pct = (i + 1)*100/entities.Count;
                    this.OnStatusChanged(Guid.Empty, this.GetType(), null, string.Empty, "Rule based collapse calculator", msg, pct);
                }
            }

            List<string> orphanEntityIDs = new List<string>();
            foreach(Entity entity in entities.Values)
            {
                orphanEntityIDs.Add(entity.Id);
            }

            while(orphanEntityIDs.Count>0)
            {
                List<string> groupedEntityIDs = new List<string>();
                string seedEntityID = orphanEntityIDs[0];
                groupedEntityIDs.Add(seedEntityID);
                this.GetAssociatedEntityIDs(
                    entity_entity_similarities,
                    orphanEntityIDs,
                    seedEntityID,
                    ref groupedEntityIDs);
                int newCollapseID = this.NextCollapseID(collapsedEntities);
                Dictionary<string, object> collapseInstanceAttrValues = new Dictionary<string, object>();
                foreach(AttributeMetaData attrMetaData in attrMetaDatas)
                {
                    
                    if(attrMetaData.AttributeType==AttributeType.CompositeAttribute)
                    {
                        foreach(string attrName in attrMetaData.ArgNameAttrNameBindings.Values)
                        {
                            object[] attrValues = new object[groupedEntityIDs.Count];
                            for (int i = 0; i < groupedEntityIDs.Count; i++)
                            {
                                attrValues[i] = entities[groupedEntityIDs[i]].ComparableAttributes[attrName];
                            }
                            object attrMode = attrMetaData.GetAttributeModeValue(attrValues);
                            collapseInstanceAttrValues.Add(attrName, attrMode);
                        }
                    }
                    else
                    {
                        string attrName = attrMetaData.AttributeName;
                        object[] attrValues = new object[groupedEntityIDs.Count];
                        for (int i = 0; i < groupedEntityIDs.Count; i++)
                        {
                            attrValues[i] = entities[groupedEntityIDs[i]].ComparableAttributes[attrName];
                        }
                        object attrMode = attrMetaData.GetAttributeModeValue(attrValues);
                        collapseInstanceAttrValues.Add(attrName, attrMode);
                    }
                    
                }
                collapsedInstances.Add(newCollapseID, collapseInstanceAttrValues);
                Dictionary<string, double> collapsedEntitySimilarities = new Dictionary<string, double>();
                foreach (string entityID in groupedEntityIDs)
                {
                    double similaritySum = 0;
                    double attrWeightSum = 0;
                    Entity entity = entities[entityID];
                    Dictionary<string, object> collapsedInstanceAttrValues = new Dictionary<string, object>();
                    foreach (AttributeMetaData attrMetaData in attrMetaDatas)
                    {
                        if(attrMetaData.AttributeType==AttributeType.CompositeAttribute)
                        {
                            foreach(string attrName in attrMetaData.ArgNameAttrNameBindings.Values)
                            {
                                object collapsedAttrValue = collapseInstanceAttrValues[attrName];  
                                collapsedInstanceAttrValues.Add(attrName, collapsedAttrValue);
                            }
                        }
                        else
                        {
                            string attrName = attrMetaData.AttributeName;
                            object collapsedAttrValue = collapseInstanceAttrValues[attrName];
                            collapsedInstanceAttrValues.Add(attrName, collapsedAttrValue);
                        }
                    }
                    Entity collapsedInstance = new Entity("", collapsedInstanceAttrValues);
                    foreach(AttributeMetaData attrMetaData in attrMetaDatas)
                    {
                        IAttributeDistanceCalcutor comparer = attrComparers[attrMetaData.AttributeName];
                        MatchResult matchResult = MatchResult.NullMatch;
                        double attrSimilarity =
                            comparer.Compare(collapsedInstance, entity, attrMetaData, ref matchResult);
                        if (matchResult == MatchResult.PositiveMatch || matchResult == MatchResult.PerfectMatch)
                        {
                            similaritySum += attrMetaData.PositiveMatchWeight * attrSimilarity;
                            attrWeightSum += Math.Abs(attrMetaData.PositiveMatchWeight);
                        }
                        else if (matchResult == MatchResult.NegativeMatch)
                        {
                            similaritySum += attrMetaData.NegativeMatchWeight;
                            attrWeightSum += Math.Abs(attrMetaData.NegativeMatchWeight);
                        }
                    }

                    if(attrWeightSum>0)
                    {
                        collapsedEntitySimilarities.Add(entityID, similaritySum/attrWeightSum);
                    }
                    else
                    {
                        collapsedEntitySimilarities.Add(entityID, 0);
                    }
                }
                collapsedEntities.Add(newCollapseID, collapsedEntitySimilarities);

                foreach (string groupedEntityID in groupedEntityIDs)
                {
                    orphanEntityIDs.Remove(groupedEntityID);
                }
            }
        }

        public bool CanCollapse(
            List<AttributeMetaData> attrMetaDatas,
            Dictionary<string, IAttributeDistanceCalcutor> attrComparers,
            double nativeAttrThreshold,
            double associativeAttrThreshold, double collapseScoreThreshold,
            Entity entity1, Entity entity2, ref double similarityScore)
        {
            similarityScore = 0;
            double nativeAttrSimilaritySum = 0;
            double associativeAttrSimilaritySum = 0;
            StringBuilder trace1=new StringBuilder();
            if (!string.IsNullOrEmpty(entity1.Trace))
            {
                trace1.Append(entity1.Trace);
                trace1.AppendLine();
            }
            trace1.AppendLine(string.Format("Compare to #{0}", entity2.Id));

            StringBuilder trace2 = new StringBuilder();
            if (!string.IsNullOrEmpty(entity2.Trace))
            {
                trace2.Append(entity2.Trace);
                trace2.AppendLine();
            }
            trace2.AppendLine(string.Format("Compare to #{0}", entity1.Id));

            foreach (AttributeMetaData attrMetaData in attrMetaDatas)
            {
                IAttributeDistanceCalcutor comparer = attrComparers[attrMetaData.AttributeName];
                MatchResult matchResult = MatchResult.NullMatch;
                double attSimilarity = comparer.Compare(
                    entity1, entity2, attrMetaData, ref matchResult);
                if(matchResult==MatchResult.NegativeMatch)
                {
                    trace1.AppendLine(string.Format("({0}:{1})",
                                                    attrMetaData.AttributeName,
                                                    attSimilarity*attrMetaData.NegativeMatchWeight));
                    trace2.AppendLine(string.Format("({0}:{1})", attrMetaData.AttributeName,
                                                    attSimilarity * attrMetaData.NegativeMatchWeight));
                }
                else
                {
                    trace1.AppendLine(string.Format("({0}:{1})", attrMetaData.AttributeName,
                                                    attSimilarity * attrMetaData.PositiveMatchWeight));
                    trace2.AppendLine(string.Format("({0}:{1})", attrMetaData.AttributeName,
                                                    attSimilarity * attrMetaData.PositiveMatchWeight));    
                }
                
                if (attrMetaData.AttributeType == AttributeType.NativeAttribute)
                {
                    if (matchResult == MatchResult.PerfectMatch || matchResult == MatchResult.PositiveMatch)
                        nativeAttrSimilaritySum += attSimilarity*attrMetaData.PositiveMatchWeight;
                    else if (matchResult == MatchResult.NegativeMatch)
                        nativeAttrSimilaritySum += attrMetaData.NegativeMatchWeight;
                }
                else
                {
                    if (matchResult == MatchResult.PerfectMatch || matchResult == MatchResult.PositiveMatch)
                        associativeAttrSimilaritySum += attSimilarity*attrMetaData.PositiveMatchWeight;
                    else if (matchResult == MatchResult.NegativeMatch)
                        associativeAttrSimilaritySum += attrMetaData.NegativeMatchWeight;
                }
            }
            if (nativeAttrSimilaritySum >= nativeAttrThreshold)
                similarityScore += nativeAttrSimilaritySum;
            if (associativeAttrSimilaritySum >= associativeAttrThreshold)
                similarityScore += associativeAttrSimilaritySum;
            if (similarityScore >= collapseScoreThreshold)
            {
                entity1.Trace = trace1.ToString();
                entity2.Trace = trace2.ToString();
                return true;
            }
            else
            {
                return false;
            }
        }

        public bool CanMerge(Entity entity1, Entity entity2,
            Dictionary<string, AttributeMetaData> attrMetaDatas,
            Dictionary<string, IAttributeDistanceCalcutor> attrComparers,
            List<List<string>> mergeableAttributeNames)
        {
            for(int i=0;i<mergeableAttributeNames.Count;i++)
            {
                List<string> attrNames = mergeableAttributeNames[i];
                int attrValMatchedCount = 0;
                foreach(string attrName in attrNames)
                {
                    MatchResult result = MatchResult.NullMatch;
                    attrComparers[attrName].Compare(entity1, entity2, attrMetaDatas[attrName], ref result);
                    if(result==MatchResult.PositiveMatch || result==MatchResult.PerfectMatch)
                    {
                        attrValMatchedCount++;
                    }
                }
                if(attrValMatchedCount==attrNames.Count)
                    return true;
            }
            return false;
        }

        #endregion

        private void GetAssociatedEntityIDs(
            Dictionary<string, Dictionary<string, double>> entity_entity_similarities,
            List<string> scopeEntityIDs,
            string seedEntityID,
            ref List<string> groupedEntityIDs)
        {
            List<string> relatedEntityIDs = new List<string>();
            Dictionary<string, double> toEntitySimilarities = entity_entity_similarities[seedEntityID];

            if(entity_entity_similarities.ContainsKey(seedEntityID))
            {
                foreach (string toEntityID in toEntitySimilarities.Keys)
                {
                    if(!groupedEntityIDs.Contains(toEntityID) && scopeEntityIDs.Contains(toEntityID))
                    {
                        relatedEntityIDs.Add(toEntityID);
                        groupedEntityIDs.Add(toEntityID);
                    }
                }
            }
            foreach(string fromEntityID in entity_entity_similarities.Keys)
            {
                if(entity_entity_similarities[fromEntityID].ContainsKey(seedEntityID))
                {
                    if(!groupedEntityIDs.Contains(fromEntityID) && 
                        scopeEntityIDs.Contains(fromEntityID))
                    {
                        relatedEntityIDs.Add(fromEntityID);
                        groupedEntityIDs.Add(fromEntityID);
                    }
                }
            }

            if (relatedEntityIDs.Count > 0)
            {
                foreach (string toEntityID in relatedEntityIDs)
                {
                    this.GetAssociatedEntityIDs(
                        entity_entity_similarities,
                        scopeEntityIDs,
                        toEntityID,
                        ref groupedEntityIDs);
                }
            }
        }

        private int NextCollapseID(Dictionary<int, Dictionary<string, double>> collapsedEntities)
        {
            int lastCollapseID = 0;
            foreach(int collapseID in collapsedEntities.Keys)
            {
                if (collapseID > lastCollapseID)
                    lastCollapseID = collapseID;
            }
            return lastCollapseID + 1;
        }
    }
}
